// ======================================================================
// \title Os/File.hpp
// \brief common function definitions for Os::File
// ======================================================================
#ifndef Os_File_hpp_
#define Os_File_hpp_

#include <Fw/FPrimeBasicTypes.hpp>
#include <Os/Os.hpp>

// Forward declaration for UTs
namespace Os {
namespace Test {
namespace FileTest {
struct Tester;
}
}  // namespace Test
}  // namespace Os

namespace Os {

//! \brief base implementation of FileHandle
//!
struct FileHandle {};

// This class encapsulates a very simple file interface that has the most often-used features
class FileInterface {
  public:
    enum Mode {
        OPEN_NO_MODE,     //!<  File mode not yet selected
        OPEN_READ,        //!<  Open file for reading
        OPEN_CREATE,      //!< Open file for writing and truncates file if it exists, ie same flags as creat()
        OPEN_WRITE,       //!<  Open file for writing
        OPEN_SYNC_WRITE,  //!<  Open file for writing; writes don't return until data is on disk
        OPEN_APPEND,      //!< Open file for appending
        MAX_OPEN_MODE     //!< Maximum value of mode
    };

    enum Status {
        OP_OK,              //!<  Operation was successful
        DOESNT_EXIST,       //!<  File doesn't exist (for read)
        NO_SPACE,           //!<  No space left
        NO_PERMISSION,      //!<  No permission to read/write file
        BAD_SIZE,           //!<  Invalid size parameter
        NOT_OPENED,         //!<  file hasn't been opened yet
        FILE_EXISTS,        //!< file already exist (for CREATE with O_EXCL enabled)
        NOT_SUPPORTED,      //!< Kernel or file system does not support operation
        INVALID_MODE,       //!< Mode for file access is invalid for current operation
        INVALID_ARGUMENT,   //!< Invalid argument passed in
        NO_MORE_RESOURCES,  //!< No more available resources
        OTHER_ERROR,        //!<  A catch-all for other errors. Have to look in implementation-specific code
        MAX_STATUS          //!< Maximum value of status
    };

    enum OverwriteType {
        NO_OVERWRITE,  //!< Do NOT overwrite existing files
        OVERWRITE,     //!< Overwrite file when it exists and creation was requested
        MAX_OVERWRITE_TYPE
    };

    enum SeekType {
        RELATIVE,  //!< Relative seek from current file offset
        ABSOLUTE,  //!< Absolute seek from beginning of file
        MAX_SEEK_TYPE
    };

    enum WaitType {
        NO_WAIT,  //!< Do not wait for read/write operation to finish
        WAIT,     //!< Do wait for read/write operation to finish
        MAX_WAIT_TYPE
    };

    virtual ~FileInterface() = default;

    //! \brief open file with supplied path and mode
    //!
    //! Open the file passed in with the given mode. If overwrite is set to OVERWRITE, then opening files in
    //! OPEN_CREATE mode will clobber existing files. Set overwrite to NO_OVERWRITE to preserve existing files.
    //! The status of the open request is returned from the function call. Delegates to the chosen
    //! implementation's `open` function.
    //!
    //! It is invalid to send `nullptr` as the path.
    //! It is invalid to supply `mode` as a non-enumerated value.
    //! It is invalid to supply `overwrite` as a non-enumerated value.
    //!
    //! \param path: c-string of path to open
    //! \param mode: file operation mode
    //! \param overwrite: overwrite existing file on create
    //! \return: status of the open
    //!
    virtual Status open(const char* path, Mode mode, OverwriteType overwrite) = 0;

    //! \brief close the file, if not opened then do nothing
    //!
    //! Closes the file, if open. Otherwise this function does nothing. Delegates to the chosen implementation's
    //! `closeInternal` function. `mode` is set to `OPEN_NO_MODE`.
    //!
    virtual void close() = 0;

    //! \brief get size of currently open file
    //!
    //! Get the size of the currently open file and fill the size parameter. Return status of the operation.
    //! \param size: output parameter for size.
    //! \return OP_OK on success otherwise error status
    //!
    virtual Status size(FwSizeType& size_result) = 0;

    //! \brief get file pointer position of the currently open file
    //!
    //! Get the current position of the read/write pointer of the open file.
    //! \param position: output parameter for size.
    //! \return OP_OK on success otherwise error status
    //!
    virtual Status position(FwSizeType& position_result) = 0;

    //! \brief pre-allocate file storage
    //!
    //! Pre-allocates file storage with at least `length` storage starting at `offset`. No-op on implementations
    //! that cannot pre-allocate.
    //!
    //! It is invalid to pass a negative `offset`.
    //! It is invalid to pass a negative `length`.
    //!
    //! \param offset: offset into file
    //! \param length: length after offset to preallocate
    //! \return OP_OK on success otherwise error status
    //!
    virtual Status preallocate(FwSizeType offset, FwSizeType length) = 0;

    //! \brief seek the file pointer to the given offset
    //!
    //! Seek the file pointer to the given `offset`. If `seekType` is set to `ABSOLUTE` then the offset is calculated
    //! from the start of the file, and if it is set to `RELATIVE` it is calculated from the current position.
    //!
    //! \param offset: offset to seek to
    //! \param seekType: `ABSOLUTE` for seeking from beginning of file, `RELATIVE` to use current position.
    //! \return OP_OK on success otherwise error status
    //!
    virtual Status seek(FwSignedSizeType offset, SeekType seekType) = 0;

    //! \brief flush file contents to storage
    //!
    //! Flushes the file contents to storage (i.e. out of the OS cache to disk). Does nothing in implementations
    //! that do not support flushing.
    //!
    //! \return OP_OK on success otherwise error status
    //!
    virtual Status flush() = 0;

    //! \brief read data from this file into supplied buffer bounded by size
    //!
    //! Read data from this file up to the `size` and store it in `buffer`.  When `wait` is set to `WAIT`, this
    //! will block until the requested size has been read successfully read or the end of the file has been
    //! reached. When `wait` is set to `NO_WAIT` it will return whatever data is currently available.
    //!
    //! `size` will be updated to the count of bytes actually read. Status will reflect the success/failure of
    //! the read operation.
    //!
    //! It is invalid to pass `nullptr` to this function call.
    //! It is invalid to pass a negative `size`.
    //! It is invalid to supply wait as a non-enumerated value.
    //!
    //! \param buffer: memory location to store data read from file
    //! \param size: size of data to read
    //! \param wait: `WAIT` to wait for data, `NO_WAIT` to return what is currently available
    //! \return OP_OK on success otherwise error status
    //!
    virtual Status read(U8* buffer, FwSizeType& size, WaitType wait) = 0;

    //! \brief read data from this file into supplied buffer bounded by size
    //!
    //! Write data to this file up to the `size` from the `buffer`.  When `wait` is set to `WAIT`, this
    //! will block until the requested size has been written successfully to disk. When `wait` is set to
    //! `NO_WAIT` it will return once the data is sent to the OS.
    //!
    //! `size` will be updated to the count of bytes actually written. Status will reflect the success/failure of
    //! the read operation.
    //!
    //! It is invalid to pass `nullptr` to this function call.
    //! It is invalid to pass a negative `size`.
    //! It is invalid to supply wait as a non-enumerated value.
    //!
    //! \param buffer: memory location to store data read from file
    //! \param size: size of data to read
    //! \param wait: `WAIT` to wait for data to write to disk, `NO_WAIT` to return what is currently available
    //! \return OP_OK on success otherwise error status
    //!
    virtual Status write(const U8* buffer, FwSizeType& size, WaitType wait) = 0;

    //! \brief returns the raw file handle
    //!
    //! Gets the raw file handle from the implementation. Note: users must include the implementation specific
    //! header to make any real use of this handle. Otherwise it will be as an opaque type.
    //!
    //! \return raw file handle
    //!
    virtual FileHandle* getHandle() = 0;

    //! \brief provide a pointer to a file delegate object
    //!
    //! This function must return a pointer to a `FileInterface` object that contains the real implementation of the
    //! file functions as defined by the implementor.  This function must do several things to be considered correctly
    //! implemented:
    //!
    //! 1. Assert that the supplied memory is non-null. e.g `FW_ASSERT(aligned_placement_new_memory != NULL);`
    //! 2. Assert that their implementation fits within FW_HANDLE_MAX_SIZE.
    //!    e.g. `static_assert(sizeof(PosixFileImplementation) <= sizeof Os::File::m_handle_storage,
    //!        "FW_HANDLE_MAX_SIZE too small");`
    //! 3. Assert that their implementation aligns within FW_HANDLE_ALIGNMENT.
    //!    e.g. `static_assert((FW_HANDLE_ALIGNMENT % alignof(PosixFileImplementation)) == 0, "Bad handle alignment");`
    //! 4. If to_copy is null, placement new their implementation into `aligned_placement_new_memory`
    //!    e.g. `FileInterface* interface = new (aligned_placement_new_memory) PosixFileImplementation;`
    //! 5. If to_copy is non-null, placement new using copy constructor their implementation into
    //!    `aligned_placement_new_memory`
    //!    e.g. `FileInterface* interface = new (aligned_placement_new_memory) PosixFileImplementation(*to_copy);`
    //! 6. Return the result of the placement new
    //!    e.g. `return interface;`
    //!
    //! \return result of placement new, must be equivalent to `aligned_placement_new_memory`
    //!
    static FileInterface* getDelegate(FileHandleStorage& aligned_placement_new_memory,
                                      const FileInterface* to_copy = nullptr);
};

class File final : public FileInterface {
    friend struct Os::Test::FileTest::Tester;

  public:
    //! \brief constructor
    //!
    File();
    //! \brief destructor
    //!
    //! Destructor closes the file if it is open
    ~File() final;

    //! \brief copy constructor that copies the internal representation
    File(const File& other);

    //! \brief assignment operator that copies the internal representation
    File& operator=(const File& other);

    //! \brief determine if the file is open
    //! \return true if file is open, false otherwise
    //!
    bool isOpen() const;

    // ------------------------------------
    // Functions supplying default values
    // ------------------------------------

    //! \brief open file with supplied path and mode
    //!
    //! Open the file passed in with the given mode. Opening files with `OPEN_CREATE` mode will not clobber existing
    //! files. Use other `open` method to set overwrite flag and clobber existing files. The status of the open
    //! request is returned from the function call. Delegates to the chosen implementation's `open` function.
    //!
    //! It is invalid to send `nullptr` as the path.
    //! It is invalid to supply `mode` as a non-enumerated value.
    //!
    //! \param path: c-string of path to open
    //! \param mode: file operation mode
    //! \return: status of the open
    //!
    Os::FileInterface::Status open(const char* path, Mode mode);

    //! \brief read data from this file into supplied buffer bounded by size
    //!
    //! Read data from this file up to the `size` and store it in `buffer`.  This version will
    //! will block until the requested size has been read successfully read or the end of the file has been
    //! reached.
    //!
    //! `size` will be updated to the count of bytes actually read. Status will reflect the success/failure of
    //! the read operation.
    //!
    //! It is invalid to pass `nullptr` to this function call.
    //! It is invalid to pass a negative `size`.
    //!
    //! \param buffer: memory location to store data read from file
    //! \param size: size of data to read
    //! \return OP_OK on success otherwise error status
    //!
    Status read(U8* buffer, FwSizeType& size);

    //! \brief write data to this file from the supplied buffer bounded by size
    //!
    //! Write data from `buffer` up to the `size` and store it in this file. This call
    //! will block until the requested size has been written. Otherwise, this call will write without blocking.
    //!
    //! `size` will be updated to the count of bytes actually written. Status will reflect the success/failure of
    //! the write operation.
    //!
    //! It is invalid to pass `nullptr` to this function call.
    //! It is invalid to pass a negative `size`.
    //!
    //! \param buffer: memory location of data to write to file
    //! \param size: size of data to write
    //! \return OP_OK on success otherwise error status
    //!
    Status write(const U8* buffer, FwSizeType& size);

    // ------------------------------------
    // Functions overrides
    // ------------------------------------

    //! \brief open file with supplied path and mode
    //!
    //! Open the file passed in with the given mode. If overwrite is set to OVERWRITE, then opening files in
    //! OPEN_CREATE mode will clobber existing files. Set overwrite to NO_OVERWRITE to preserve existing files.
    //! The status of the open request is returned from the function call. Delegates to the chosen
    //! implementation's `open` function.
    //!
    //! It is invalid to send `nullptr` as the path.
    //! It is invalid to supply `mode` as a non-enumerated value.
    //! It is invalid to supply `overwrite` as a non-enumerated value.
    //!
    //! \param path: c-string of path to open
    //! \param mode: file operation mode
    //! \param overwrite: overwrite existing file on create
    //! \return: status of the open
    //!
    Os::FileInterface::Status open(const char* path, Mode mode, OverwriteType overwrite) override;

    //! \brief close the file, if not opened then do nothing
    //!
    //! Closes the file, if open. Otherwise this function does nothing. Delegates to the chosen implementation's
    //! `closeInternal` function. `mode` is set to `OPEN_NO_MODE`.
    //!
    void close() override;

    //! \brief get size of currently open file
    //!
    //! Get the size of the currently open file and fill the size parameter. Return status of the operation.
    //! \param size: output parameter for size.
    //! \return OP_OK on success otherwise error status
    //!
    Status size(FwSizeType& size_result) override;

    //! \brief get file pointer position of the currently open file
    //!
    //! Get the current position of the read/write pointer of the open file.
    //! \param position: output parameter for size.
    //! \return OP_OK on success otherwise error status
    //!
    Status position(FwSizeType& position_result) override;

    //! \brief pre-allocate file storage
    //!
    //! Pre-allocates file storage with at least `length` storage starting at `offset`. No-op on implementations
    //! that cannot pre-allocate.
    //!
    //! It is invalid to pass a negative `offset`.
    //! It is invalid to pass a negative `length`.
    //!
    //! \param offset: offset into file
    //! \param length: length after offset to preallocate
    //! \return OP_OK on success otherwise error status
    //!
    Status preallocate(FwSizeType offset, FwSizeType length) override;

    //! \brief seek the file pointer to the given offset
    //!
    //! Seek the file pointer to the given `offset`. If `seekType` is set to `ABSOLUTE` then the offset is calculated
    //! from the start of the file, and if it is set to `RELATIVE` it is calculated from the current position.
    //!
    //! \param offset: offset to seek to
    //! \param seekType: `ABSOLUTE` for seeking from beginning of file, `RELATIVE` to use current position.
    //! \return OP_OK on success otherwise error status
    //!
    Status seek(FwSignedSizeType offset, SeekType seekType) override;

    //! \brief seek the file pointer to the given offset absolutely with the full range
    //!
    //! Seek the file pointer to the given `offset` absolutely from the beginning of the file. This function is
    //! equivalent to calling `seek` with `ABSOLUTE` as the `seekType` with the exception that it can handle the
    //! full range of `FwSizeType` values as returned by `size` and `position` calls.
    //!
    //! Internally, it will perform multiple seeks to reach the desired offset while never exceeding the signed
    //! limit of the basic `seek` function.
    //!
    //! \param offset_unsigned: offset to absolutely seek to
    //! \return OP_OK on success otherwise error status
    Status seek_absolute(FwSizeType offset_unsigned);

    //! \brief flush file contents to storage
    //!
    //! Flushes the file contents to storage (i.e. out of the OS cache to disk). Does nothing in implementations
    //! that do not support flushing.
    //!
    //! \return OP_OK on success otherwise error status
    //!
    Status flush() override;

    //! \brief read data from this file into supplied buffer bounded by size
    //!
    //! Read data from this file up to the `size` and store it in `buffer`.  When `wait` is set to `WAIT`, this
    //! will block until the requested size has been read successfully read or the end of the file has been
    //! reached. When `wait` is set to `NO_WAIT` it will return whatever data is currently available.
    //!
    //! `size` will be updated to the count of bytes actually read. Status will reflect the success/failure of
    //! the read operation.
    //!
    //! It is invalid to pass `nullptr` to this function call.
    //! It is invalid to pass a negative `size`.
    //! It is invalid to supply wait as a non-enumerated value.
    //!
    //! \param buffer: memory location to store data read from file
    //! \param size: size of data to read
    //! \param wait: `WAIT` to wait for data, `NO_WAIT` to return what is currently available

    //!
    Status read(U8* buffer, FwSizeType& size, WaitType wait) override;

    //! \brief read a line from the file using `\n` as the delimiter
    //!
    //! Reads a single line from the file including the terminating '\n'. This will return an error if no line is
    //! found within the specified buffer size. In the case of EOF, the line is read without the terminating '\n'.
    //!
    //! In the case of an error, this function will seek to the original location in the file. Otherwise, the
    //! pointer will point to the first character after the `\n` or EOF in the case of no `\n`.
    //!
    //! It is invalid to send a null buffer.
    //! It is invalid to send a size less than 0.
    //! It is an error if the file is not opened for reading.
    //!
    //! \param buffer: memory location to store data read from file
    //! \param size: maximum size of buffer to store the new line
    //! \param wait: `WAIT` to wait for data, `NO_WAIT` to return what is currently available
    //! \return OP_OK on success otherwise error status
    Status readline(U8* buffer, FwSizeType& size, WaitType wait);

    //! \brief read data from this file into supplied buffer bounded by size
    //!
    //! Write data to this file up to the `size` from the `buffer`.  When `wait` is set to `WAIT`, this
    //! will block until the requested size has been written successfully to disk. When `wait` is set to
    //! `NO_WAIT` it will return once the data is sent to the OS.
    //!
    //! `size` will be updated to the count of bytes actually written. Status will reflect the success/failure of
    //! the read operation.
    //!
    //! It is invalid to pass `nullptr` to this function call.
    //! It is invalid to pass a negative `size`.
    //! It is invalid to supply wait as a non-enumerated value.
    //!
    //! \param buffer: memory location to store data read from file
    //! \param size: size of data to read
    //! \param wait: `WAIT` to wait for data to write to disk, `NO_WAIT` to return what is currently available
    //! \return OP_OK on success otherwise error status
    //!
    Status write(const U8* buffer, FwSizeType& size, WaitType wait) override;

    //! \brief returns the raw file handle
    //!
    //! Gets the raw file handle from the implementation. Note: users must include the implementation specific
    //! header to make any real use of this handle. Otherwise it//!must* be passed as an opaque type.
    //!
    //! \return raw file handle
    //!
    FileHandle* getHandle() override;

    //! \brief calculate the CRC32 of the entire file
    //!
    //! Calculates the CRC32 of the file's contents. The `crc` parameter will be updated to contain the CRC or 0 on
    //! failure. Status will represent failure conditions. This call will be decomposed into calculations on
    //! sections of the file `FW_FILE_CHUNK_SIZE` bytes long.
    //!
    //! This function requires that the file already be opened for "READ" mode.
    //!
    //! On error crc will be set to 0.
    //!
    //! \note: the file pointer will be positioned at the end of the file after this call.
    //!
    //! This function is equivalent to the following pseudo-code:
    //!
    //! ```
    //! U32 crc;
    //! do {
    //!     size = FW_FILE_CHUNK_SIZE;
    //!     m_file.incrementalCrc(size);
    //! while (size == FW_FILE_CHUNK_SIZE);
    //! m_file.finalize(crc);
    //! ```
    //! \param crc: U32 bit value to fill with CRC
    //! \return OP_OK on success otherwise error status
    //!
    Status calculateCrc(U32& crc);

    //! \brief calculate the CRC32 of the next section of data
    //!
    //! Starting at the current file pointer, this will add `size` bytes of data to the currently calculated CRC.
    //! Call `finalizeCrc` to retrieve the CRC or `calculateCrc` to perform a CRC on the entire file. This call will
    //! not block waiting for data on the underlying read, nor will it reset the file position pointer. On error,
    //! the current CRC results should be discarded by reopening the file or calling `finalizeCrc` and
    //! discarding its result. `size` will be updated with the `size` actually read and used in the CRC calculation.
    //!
    //! This function requires that the file already be opened for "READ" mode.
    //!
    //! It is illegal for size to be less than or equal to 0 or greater than FW_FILE_CHUNK_SIZE.
    //!
    //! \param size: size of data to read for CRC
    //! \return: status of the CRC calculation
    //!
    Status incrementalCrc(FwSizeType& size);

    //! \brief finalize and retrieve the CRC value
    //!
    //! Finalizes the CRC computation and returns the CRC value. The `crc` value will be modified to contain the
    //! crc or 0 on error. Note: this will reset any active CRC calculation and effectively re-initializes any
    //! `incrementalCrc` calculation.
    //!
    //! On error crc will be set to 0.
    //!
    //! \param crc: value to fill
    //! \return status of the CRC calculation
    //!
    Status finalizeCrc(U32& crc);

  private:
    static const U32 INITIAL_CRC = 0xFFFFFFFF;  //!< Initial value for CRC calculation

    Mode m_mode = Mode::OPEN_NO_MODE;  //!< Stores mode for error checking
    const CHAR* m_path = nullptr;      //!< Path last opened

    U32 m_crc = File::INITIAL_CRC;  //!< Current CRC calculation
    U8 m_crc_buffer[FW_FILE_CHUNK_SIZE];

    // This section is used to store the implementation-defined file handle. To Os::File and fprime, this type is
    // opaque and thus normal allocation cannot be done. Instead, we allow the implementor to store then handle in
    // the byte-array here and set `handle` to that address for storage.
    //
    alignas(FW_HANDLE_ALIGNMENT) FileHandleStorage m_handle_storage;  //!< Storage for aligned FileHandle data
    FileInterface& m_delegate;                                        //!< Delegate for the real implementation
};
}  // namespace Os
#endif
